﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Input;
using System.Windows.Threading;
using Halfwit.Pollers;
using Halfwit.Views;
using TweetSharp.Twitter.Extensions;
using TweetSharp.Twitter.Fluent;
using TweetSharp.Twitter.Model;
using System.Windows.Controls;
using System.Text.RegularExpressions;

namespace Halfwit.ViewModels
{
    public abstract class TweetsViewModel : INotifyPropertyChanged
    {
        public TweetsViewModel(MainViewModel host)
        {
            _host = host;

            this.Pollers = new List<TwitterPoller>();

            this.Title = "Tweets";
            this.Uri = "http://twitter.com/";

            this.Connection = _host.Connection;
            this.Options = _host.Options;
            this.About = _host.About;
            this.NavigateToHome = _host.Home;
            this.NavigateToMentions = _host.Mentions;
            this.NavigateToMessages = _host.Messages;
            this.NavigateToSearch = _host.Search;
            this.NavigateToUrl = _host.OpenUri;

            this.Refresh = new RelayCommand("Refresh", RetrieveTweets);
            this.Tweet = new RelayCommand("Tweet", PostTweet, CanPostTweet);
            this.Copy = new RelayCommand("Copy", CopyTweet);
            this.NavigateToUser = new RelayCommand("Show User", ShowUserTweets);
            this.Reply = new RelayCommand("Reply", PostReply);
            this.Retweet = new RelayCommand("Retweet", PostRetweet);
            this.Favourite = new RelayCommand("Favourite", ToggleFavourite, CanToggleFavourite);
            this.Delete = new RelayCommand("Delete", DeleteTweet, CanDeleteTweet);
            this.Follow = new RelayCommand("Follow", ToggleFollow, CanToggleFollow);
            this.DirectMessage = new RelayCommand("Message", SendMessage, CanSendMessage);
            this.FindSource = new RelayCommand("Find Source Tweet", FindOrLoadSource, CanFindSource);
            this.InsertText = new RelayCommand("Insert Text", InsertTweetText);
            this.ShortenUrl = new RelayCommand("Shorten URL", Shorten, _ => UpdateTextContainsUrl);

            this.InputBindings = new List<InputBinding>();
            InputBindings.Add(new KeyBinding(this.Refresh, new KeyGesture(Key.F5)));
            InputBindings.Add(new KeyBinding(this.NavigateToHome, new KeyGesture(Key.D1, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.NavigateToMentions, new KeyGesture(Key.D2, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.NavigateToMessages, new KeyGesture(Key.D3, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.NavigateToUser, new KeyGesture(Key.D4, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.NavigateToSearch, new KeyGesture(Key.D5, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.NavigateToUrl, new KeyGesture(Key.D6, ModifierKeys.Control)));
            InputBindings.Add(new KeyBinding(this.ShortenUrl, new KeyGesture(Key.U, ModifierKeys.Control | ModifierKeys.Shift)));

            //System.Diagnostics.Trace.WriteLine(GetType().Name + " initialized");
        }

        //~TweetsViewModel()
        //{
        //    System.Diagnostics.Trace.WriteLine(GetType().Name + " finalized");
        //}

        private MainViewModel _host;
        private TwitterStatus _selectedItem;
        private bool _updating;
        private string _updateText = String.Empty;
        private int _selectionStart;
        private int _selectionLength;
        private TwitterUpdateContext _context;
        private string _status;
        private TwitterRateLimitStatus _rateLimitStatus;
        private ObservableCollection<TwitterStatus> _tweets = new ObservableCollection<TwitterStatus>();

        private Regex _urlRegex = new Regex(@"(http|ftp|https):\/\/[\w\-_]+(\.[\w\-_]+)+([\w\-\.,@?^=%&amp;:/~\+#]*[\w\-\@?^=%&amp;/~\+#])?", RegexOptions.Compiled);

        private void CopyTweet(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            var text = "@" + tweet.User.ScreenName + ": ";
            if (tweet.RetweetedStatus != null)
            {
                text += "RT @" + tweet.RetweetedStatus.User.ScreenName + ": " + tweet.RetweetedStatus.Text;
            }
            else
            {
                text += tweet.Text;
            }
            Clipboard.SetText(text);
        }

        private void RetrieveTweets(object o)
        {
            this.Status = "Refreshing...";

            var polled = false;
            foreach (var p in Pollers)
            {
                polled = p.Poll() || polled;
            }

            if (!polled)
            {
                // none of the pollers polled, so don't leave "Refreshing" on the status bar
                this.Status = String.Empty;
            }
        }

        private void ShowUserTweets(object o)
        {
            var username = o as string;
            if (username == null) username = TwitterConnection.User.ScreenName;

            var request = TwitterConnection.CreateRequest()
                .Users().ShowProfileFor(username);

            var result = request.Request();
            if (result.IsFailWhale || result.IsNetworkError || result.IsTwitterError)
            {
                var error = result.AsError();

                Status = error != null ? error.ErrorMessage
                    : !String.IsNullOrEmpty(result.ResponseHttpStatusDescription) ? result.ResponseHttpStatusDescription
                    : "Unknown error. Possible space-time continuum anomaly.";
                return;
            }

            var user = result.AsUser();
            if (user == null)
            {
                Status = "User not found";
                return;
            }

            var vm = new UserViewModel(_host, user);
            if (Properties.Settings.Default.OpenLinksInBrowser)
            {
                System.Diagnostics.Process.Start(vm.Uri);
                return;
            }

            _host.NavigationService.Navigate(new TweetsPage(vm));
        }

        //private void GoToUrl(object o)
        //{
        //    var text = o as String;
        //    if (text == null) text = this.Uri;
        //    if (text == null) return;

        //    try
        //    {
        //        System.Diagnostics.Process.Start(text);
        //    }
        //    catch (Exception e)
        //    {
        //        System.Diagnostics.Trace.TraceError(e.ToString());
        //    }
        //}

        private bool CanPostTweet(object o)
        {
            return !_posting && IsUpdateTextValid;
        }

        private void PostTweet(object o)
        {
            if (!IsUpdateTextValid) return;

            var request = TwitterConnection.CreateRequest();
            ITwitterLeafNode updateRequest;

            if (UpdateContext != null)
            {
                updateRequest = UpdateContext.GetRequest(request, UpdateText);
            }
            else
            {
                updateRequest = request.Statuses().Update(UpdateText);
            }

            _posting = true;
            CommandManager.InvalidateRequerySuggested();
            updateRequest.CallbackTo(TweetPosted).BeginRequest();
        }

        private bool _posting;

        private void TweetPosted(object sender, TwitterResult result, object userState)
        {
            if (App.Current.Dispatcher.Thread != Dispatcher.CurrentDispatcher.Thread)
            {
                App.Current.Dispatcher.Invoke(new Action<object, TwitterResult, object>(TweetPosted), DispatcherPriority.Background, sender, result, userState);
                return;
            }

            _posting = false;

            if (result.IsFailWhale || result.IsNetworkError || result.IsTwitterError)
            {
                this.Status = result.ResponseHttpStatusDescription;
                return;
            }

            OnUpdate(result);

            this.Status = "Tweet successfully posted";
            UpdateContext = null;
            UpdateText = string.Empty;
            IsUpdating = false;
        }

        private void PostReply(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            if (tweet.Source == HalfwitConstants.DirectMessageSource && TwitterConnection.FollowerIds.Contains(tweet.User.Id))
            {
                // it's a direct message we're replying to, so we need to try to reply with a DM

                var result = MessageBox.Show("Reply to this message via direct message?", "Confirm Reply", MessageBoxButton.YesNoCancel, MessageBoxImage.Question);

                if (result == MessageBoxResult.Cancel) return;

                if (result == MessageBoxResult.Yes)
                {
                    DirectMessage.Execute(tweet.User);
                    return;
                }
            }

            if (tweet.User.Id == TwitterConnection.UserId &&
                MessageBox.Show("Reply to yourself?", "Confirm Reply", MessageBoxButton.YesNo, MessageBoxImage.Question) == MessageBoxResult.No)
            {
                return;
            }

            IsUpdating = false;

            // Yes, this is NASTY. I shouldn't be looking at the keyboard in the ViewModel.
            // Eventually I will make a ReplyToAll ICommand instead, but this'll do for now.
            if ((Keyboard.Modifiers & ModifierKeys.Shift) == ModifierKeys.Shift)
            {
                UpdateContext = new TwitterReplyAllContext(tweet);
            }
            else
            {
                UpdateContext = new TwitterReplyContext(tweet);
            }
            UpdateText = UpdateContext.Prefix;
            IsUpdating = true;
        }

        private void PostRetweet(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            UpdateContext = new TwitterRetweetContext(tweet);
            UpdateText = UpdateContext.Prefix + System.Web.HttpUtility.HtmlDecode(tweet.Text);
            IsUpdating = true;
        }

        private bool CanToggleFavourite(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return false;

            //((RelayCommand)Favourite).Text = tweet.IsFavorited ? "Unmark as Favourite" : "Mark as Favourite";
            return true;
        }

        private void ToggleFavourite(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            var faves = TwitterConnection.CreateRequest().Favorites();
            TwitterResult result;
            if (tweet.IsFavorited)
            {
                result = faves.Unfavorite(tweet.Id).Request();
            }
            else
            {
                result = faves.Favorite(tweet.Id).Request();
            }

            if (result.IsFailWhale || result.IsNetworkError || result.IsTwitterError)
            {
                this.Status = result.ResponseHttpStatusDescription;
                return;
            }

            tweet.IsFavorited = !tweet.IsFavorited;
        }

        private bool CanDeleteTweet(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return false;

            //if (tweet.User.Id == TwitterConnection.User.Id)
            //{
            //    ((RelayCommand)Delete).Text = "Delete";
            //    return true;
            //}

            //((RelayCommand)Delete).Text = "Remove";
            return true;
        }

        private void DeleteTweet(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            Action<TwitterStatus> action;
            string prompt;

            if (tweet.User.Id == TwitterConnection.User.Id)
            {
                prompt = "Delete this update? This will permanently delete the update from Twitter.";
                action = t =>
                    {
                        var result = TwitterConnection.CreateRequest().Statuses().Destroy(t.Id).Request();
                        if (result.IsFailWhale || result.IsNetworkError || result.IsTwitterError)
                        {
                            this.Status = result.ResponseHttpStatusDescription;
                            return;
                        }
                        this.Status = "Tweet deleted";
                        Tweets.Remove(t);
                    };
            }
            else
            {
                prompt = "Remove this update from this list? This will not affect the update on Twitter.";
                action = t =>
                    {
                        Tweets.Remove(t);
                        this.Status = "Tweet removed";
                    };
            }

            if (MessageBox.Show(prompt, "Confirm Delete", MessageBoxButton.YesNo) == MessageBoxResult.No) return;

            action(tweet);
        }

        private bool CanToggleFollow(object o)
        {
            var user = o as TwitterUser;
            if (user == null) return false;

            //((RelayCommand)Follow).Text = (TwitterConnection.FriendIds.Contains(user.Id) ? "Unfollow " : "Follow ") + (user.Name ?? user.ScreenName);
            return true;
        }

        private void ToggleFollow(object o)
        {
            var user = o as TwitterUser;
            if (user == null) return;

            var friends = TwitterConnection.CreateRequest().Friendships();
            TwitterResult result;
            Action successAction;
            if (TwitterConnection.FriendIds.Contains(user.Id))
            {
                result = friends.Destroy(user.Id).Request();
                successAction = () => TwitterConnection.FriendIds.Remove(user.Id);
            }
            else
            {
                result = friends.Befriend(user.Id).Request();
                successAction = () => TwitterConnection.FriendIds.Add(user.Id);
            }

            if (result.IsFailWhale || result.IsNetworkError || result.IsTwitterError)
            {
                this.Status = result.ResponseHttpStatusDescription;
                return;
            }

            successAction();
        }

        private bool CanSendMessage(object o)
        {
            var user = o as TwitterUser;
            if (user == null) return false;

            //((RelayCommand)DirectMessage).Text = "Send " + (user.Name ?? user.ScreenName) + " a Direct Message";
            return TwitterConnection.FollowerIds.Contains(user.Id);
        }

        private void SendMessage(object o)
        {
            var user = o as TwitterUser;
            if (user == null) return;

            UpdateContext = new TwitterDirectMessageContext(user);
            UpdateText = UpdateContext.Prefix;
            IsUpdating = true;
        }

        private bool CanFindSource(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return false;

            return tweet.InReplyToStatusId.HasValue || tweet.RetweetedStatus != null || tweet.Source == HalfwitConstants.SearchResultSource;
        }

        private void FindOrLoadSource(object o)
        {
            var tweet = o as TwitterStatus;
            if (tweet == null) return;

            var id = tweet.RetweetedStatus == null ? tweet.InReplyToStatusId : tweet.RetweetedStatus.Id;

            if (!id.HasValue && tweet.Source == HalfwitConstants.SearchResultSource)
            {
                // let's reload this tweet as a full tweet rather than a search result
                id = tweet.Id;
            }
            else
            {
                var source = Tweets.FirstOrDefault(t => t.Id == id);
                if (source != null)
                {
                    SelectedItem = source;
                    return;
                }
            }

            TwitterConnection.CreateRequest()
                .Statuses()
                .Show(id.Value)
                .CallbackTo(TweetLoaded)
                .BeginRequest();
            //request.RequestAsyncSafe(TweetLoaded);
        }

        private void TweetLoaded(object sender, TwitterResult result, object userState)
        {
            if (App.Current.Dispatcher.Thread != Dispatcher.CurrentDispatcher.Thread)
            {
                App.Current.Dispatcher.Invoke(new Action<object, TwitterResult, object>(TweetLoaded), DispatcherPriority.Background, sender, result, null);
                return;
            }

            if (result.RateLimitStatus != null) this.RateLimitStatus = result.RateLimitStatus;

            if (result.IsTwitterError || result.IsNetworkError || result.IsFailWhale)
            {
                var error = result.AsError();

                Status = error != null ? error.ErrorMessage
                    : !String.IsNullOrEmpty(result.ResponseHttpStatusDescription) ? result.ResponseHttpStatusDescription
                    : "Unknown error. Possible space-time continuum anomaly.";
                return;
            }

            var tweet = result.AsStatus();
            if (tweet == null) return;

            var existingSearchResult = Tweets.FirstOrDefault(t => t.Id == tweet.Id);
            if (existingSearchResult != null)
            {
                // if this new tweet already exists as a search result, replace it
                if (existingSearchResult.Source != HalfwitConstants.SearchResultSource) return;
                Tweets.Remove(existingSearchResult);
            }

            _host.DeferRefresh(() =>
            {
                // don't need these, so let's try to save some memory
                tweet.Location = null;
                tweet.RetweetedStatus = null;

                foreach (var existingTweet in Tweets.Where(t => t.User.Id == tweet.User.Id))
                {
                    existingTweet.User = tweet.User;
                }

                Tweets.Add(tweet);
            });
            SelectedItem = tweet;
        }

        private void InsertTweetText(object o)
        {
            if (o == null) return;

            int pos = _selectionStart;

            //if (_selectionLength > 0) UpdateText = UpdateText.Remove(pos, _selectionLength);
            //UpdateText = UpdateText.Insert(pos, o.ToString());

            var textBox = _host.NavigationService.Content as ICanHasTextSelection;
            if (textBox == null) return;

            textBox.SelectedText = o.ToString();
            textBox.CaretIndex = pos + 1;
            textBox.SelectionLength = 0;
        }

        protected MainViewModel Host { get { return _host; } }
        protected IList<TwitterPoller> Pollers { get; private set; }

        protected virtual void OnUpdate(TwitterResult result)
        {
        }

        protected void PollCompleted(TwitterPoller poller, TwitterResult result)
        {
            //GC.Collect();

            if (App.Current.Dispatcher.Thread != Dispatcher.CurrentDispatcher.Thread)
            {
                System.Diagnostics.Trace.WriteLine("Didn't expect that! Invoke required inside PollCompleted!?");
                App.Current.Dispatcher.Invoke(new Action<TwitterPoller, TwitterResult>(PollCompleted), DispatcherPriority.Background, poller, result);
                return;
            }

            if (result.RateLimitStatus != null) this.RateLimitStatus = result.RateLimitStatus;

            _host.DeferRefresh(() =>
            {
                foreach (var tweet in Tweets)
                {
                    // force refresh of relative time string
                    tweet.CreatedDate = tweet.CreatedDate.AddMilliseconds(-1);
                    //tweet.OnPropertyChanged("CreatedDate");
                }
            });

            if (result.IsTwitterError || result.IsNetworkError || result.IsFailWhale)
            {
                var error = result.AsError();

                Status = error != null ? error.ErrorMessage
                    : !String.IsNullOrEmpty(result.ResponseHttpStatusDescription) ? result.ResponseHttpStatusDescription
                    : "Unknown error. Possible space-time continuum anomaly.";
                return;
            }

            var tweets = poller.ToStatuses(result);
            if (tweets != null)
            {
                TwitterConnection.AddRecentUserNames(tweets.Select(t => t.User.ScreenName));
                _host.DeferRefresh(() =>
                {
                    foreach (var tw in tweets)
                    {
                        // don't need these, so let's try to save some memory
                        tw.Location = null;

                        var existingSearchResult = Tweets.FirstOrDefault(t => t.Id == tw.Id);
                        if (existingSearchResult != null)
                        {
                            // if this new tweet already exists as a search result, replace it
                            if (existingSearchResult.Source != HalfwitConstants.SearchResultSource) continue;

                            Tweets.Remove(existingSearchResult);
                        }

                        foreach (var existingTweet in Tweets.Where(t => t.User.Id == tw.User.Id))
                        {
                            existingTweet.User = tw.User;
                        }

                        Tweets.Add(tw);
                    }

                    foreach (var tw in Tweets.OrderByDescending(t => t.CreatedDate).Skip(Halfwit.Properties.Settings.Default.MaxTweets))
                    {
                        Tweets.Remove(tw);
                    }
                });

                var count = tweets.Count();
                if (count > 0)
                {
                    _host.Notify(poller.Description + ": " + count + " new tweets");
                }
            }

            this.Status = poller.Description + " updated " + DateTime.Now;
        }

        public string Title { get; protected set; }
        public string Uri { get; protected set; }

        public ICommand Connection { get; private set; }
        public ICommand Options { get; private set; }
        public ICommand About { get; private set; }
        public ICommand Refresh { get; private set; }
        public ICommand Copy { get; private set; }
        public ICommand Tweet { get; private set; }
        public ICommand NavigateToHome { get; private set; }
        public ICommand NavigateToMentions { get; private set; }
        public ICommand NavigateToMessages { get; private set; }
        public ICommand NavigateToUser { get; private set; }
        public ICommand NavigateToSearch { get; private set; }
        public ICommand NavigateToUrl { get; private set; }
        public ICommand Reply { get; private set; }
        public ICommand Retweet { get; private set; }
        public ICommand Favourite { get; private set; }
        public ICommand Delete { get; private set; }
        public ICommand Follow { get; private set; }
        public ICommand DirectMessage { get; private set; }
        public ICommand FindSource { get; private set; }
        public ICommand InsertText { get; private set; }
        public ICommand ShortenUrl { get; private set; }

        public IList<InputBinding> InputBindings { get; private set; }

        public bool IsActive
        {
            get { return Pollers.Any(p => p.IsActive); }
            set
            {
                foreach (var p in Pollers)
                {
                    p.IsActive = value;
                }
            }
        }

        public TwitterStatus SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                _selectedItem = value;
                OnPropertyChanged("SelectedItem");
            }
        }

        public bool IsUpdating
        {
            get { return _updating; }
            set
            {
                _updating = value;

                if (!_updating)
                {
                    // clear the context of the tweet that was *maybe* posted but maybe not
                    UpdateContext = null;
                    UpdateText = String.Empty;
                }

                OnPropertyChanged("IsUpdating");
            }
        }

        public string UpdateText
        {
            get { return _updateText; }
            set
            {
                _updateText = value;
                OnPropertyChanged("UpdateText");
                OnPropertyChanged("UpdateTextContainsUrl");
                OnPropertyChanged("UpdateLength");
                OnPropertyChanged("IsUpdateTextValid");
            }
        }

        public int UpdateTextSelectionStart
        {
            get { return _selectionStart; }
            set
            {
                _selectionStart = value;
                OnPropertyChanged("UpdateTextSelectionStart");
            }
        }

        public int UpdateTextSelectionLength
        {
            get { return _selectionLength; }
            set
            {
                _selectionLength = value;
                OnPropertyChanged("UpdateTextSelectionLength");
            }
        }

        public int UpdateLength
        {
            get
            {
                var retweet = UpdateContext as TwitterRetweetContext;
                if (retweet != null)
                {
                    if (UpdateText == System.Web.HttpUtility.HtmlDecode(retweet.Prefix + retweet.Status.Text))
                    {
                        return retweet.Status.Text.Length;
                    }
                }
                return _updateText.Length;
            }
        }

        public bool IsUpdateTextValid
        {
            get
            {
                if (UpdateContext != null) return UpdateContext.IsValidText(_updateText);

                return (!String.IsNullOrEmpty(_updateText.Trim()) && _updateText.Length <= 140);
            }
        }

        public TwitterUpdateContext UpdateContext
        {
            get { return _context; }
            set
            {
                _context = value;
                OnPropertyChanged("UpdateContext");
            }
        }

        public bool UpdateTextContainsUrl
        {
            get { return _urlRegex.IsMatch(UpdateText); }
        }

        public IEnumerable<Object> ShortenUrlMenuItems
        {
            get
            {
                var matches = _urlRegex.Matches(UpdateText);
                int i = 1;

                foreach (Match match in matches)
                {
                    yield return new MenuItem
                    {
                        Command = this.ShortenUrl,
                        CommandParameter = match,
                        Header = String.Format("_{0} {1}", i++, match.Value)
                    };
                }
                yield return new Separator();
                yield return new MenuItem
                {
                    Command = this.ShortenUrl,
                    Header = "Shorten _All",
                    InputGestureText = "Ctrl+Shift+U",
                };
            }
        }

        public void Shorten(object o)
        {
            var match = o as System.Text.RegularExpressions.Match;
            if (match == null)
            {
                foreach (var m in _urlRegex.Matches(UpdateText).Cast<Match>().OrderByDescending(m => m.Index))
                {
                    ShortenMatch(m);
                }
                return;
            };

            ShortenMatch(match);
        }

        private void ShortenMatch(Match match)
        {
            var textBox = _host.NavigationService.Content as ICanHasTextSelection;
            if (textBox == null) return;

            textBox.SelectionStart = match.Index;
            textBox.SelectionLength = match.Length;
            textBox.SelectedText = new IsGd().Shorten(match.Value);
        }

        public string Status
        {
            get { return _status; }
            set
            {
                _status = value;
                OnPropertyChanged("Status");
            }
        }

        public TwitterRateLimitStatus RateLimitStatus
        {
            get { return _rateLimitStatus; }
            set
            {
                _rateLimitStatus = value;
                OnPropertyChanged("RateLimitStatus");
            }
        }

        public ICollection<TwitterStatus> Tweets
        {
            get { return _tweets; }
        }

        #region INotifyPropertyChanged Members

        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        #endregion
    }
}
